#include "rpc/net/buffer.h"

#define ALIGN_SIZE 4
#define align_size(size) \
  ((size) + ALIGN_SIZE) & ~(ALIGN_SIZE -1)

DEFINE_int32(buf_size, 4096, "buffer size");

namespace rpc {
Buffer::Buffer()
    : buffer_(FLAGS_buf_size, '\0') {
  Revert();
}

void Buffer::Revert() {
  mem_ = (u_char*) &buffer_[0];
  r_pos_ = w_pos_ = mem_;
  end_ = mem_ + buffer_.size();
}

int32 Buffer::ReadFd(int32 fd, uint32 len) {
  if (fd == -1 || len <= 0) {
    return -1;
  }
  if (WriteableSize() < len) {
    size_t old_size = buffer_.size();
    Extern(len < old_size ? old_size : len);
  }

  int32 n = ::read(fd, w_pos_, len);
  if (n > 0) {
    w_pos_ += n;
  }

  return n;
}

int32 Buffer::WriteFd(int32 fd, uint32 len) {
  if (fd < 0 || len < 0) {
    return -1;
  }

  int32 n = ::write(fd, r_pos_, len);
  if (n > 0) {
    r_pos_ += n;
  }
  return n;
}

int32 Buffer::ReadFd(int32 fd) {
  if (fd == -1) return -1;
  if (WriteableSize() == 0) {
    Extern(buffer_.size());
  }

  int32 ret;
  u_char tmp_buf[4096];
  struct iovec vec[2];
  vec[0].iov_base = w_pos_;
  vec[0].iov_len = WriteableSize();
  vec[1].iov_base = tmp_buf;
  vec[1].iov_len = 4096;

  do {
    ret = ::readv(fd, vec, 2);
    if (ret == -1 && errno != EINTR) {
      PLOG(WARNING)<< "read fd error: " << fd;
      return -1;
    }
  }while (ret == -1 && errno == EINTR);

  if (vec[1].iov_len != 0) {
    Append(tmp_buf, vec[1].iov_len);
  } else {
    w_pos_ += ret;
  }

  CHECK_LE(w_pos_, end_);
  return ret;
}

void Buffer::Resize(size_t size) {
  if (size <= 0) size = FLAGS_buf_size;
  CHECK(size > sizeof(uint32));
  size = align_size(size);
  if (FreeSize() >= size) return;

  buffer_.resize(size);
  Revert();
}

void Buffer::Extern(size_t size) {
  if (size <= 0) size = FLAGS_buf_size;
  CHECK(size > sizeof(uint32));
  size = align_size(size);

  uint32 read_pos = r_pos_ - mem_;
  uint32 write_pos = w_pos_ - mem_;

  buffer_.resize(buffer_.size() + size);
  mem_ = (u_char*) &buffer_[0];
  r_pos_ = mem_ + read_pos;
  w_pos_ = mem_ + write_pos;
  end_ = mem_ + buffer_.size();
}

const u_char* Buffer::Read(size_t size) {
  CHECK(ReadableSize() >= size);
  u_char* pos = r_pos_;
  r_pos_ += size;
  return pos;
}

const u_char* Buffer::Retrieve(size_t size) {
  CHECK(ReadableSize() >= size);
  w_pos_ -= size;
  return w_pos_;
}

u_char* Buffer::Append(uint32 size) {
  if (WriteableSize() < size) {
    Extern(buffer_.size());
  }

  u_char* pos = w_pos_;
  w_pos_ += size;
  ::memset(pos, 0, size);
  return pos;
}

void Buffer::Append(const u_char* buf, size_t size) {
  if (size > WriteableSize()) {
    if (size > (WriteableSize() + (r_pos_ - mem_))) {
      uint32 extern_size = size + ::abs(size - FLAGS_buf_size);
      Extern(extern_size);
    } else {
      u_char* pos;
      for (pos = mem_; r_pos_ != w_pos_; ++r_pos_) {
        *pos = *r_pos_;
        ++pos;
      }
      r_pos_ = mem_;
      w_pos_ = pos;
    }
  }
  CHECK_GE(WriteableSize(), size);

  memcpy(w_pos_, buf, size);
  w_pos_ += size;
}

}
